# frozen_string_literal: false

module LicenseFinder
  # Super-class for the different package managers
  # (Bundler, NPM, Pip, etc.)
  #
  # For guidance on adding a new package manager use the shared behavior
  #
  #     it_behaves_like "a PackageManager"
  #
  # Additional guidelines are:
  #
  # - implement #current_packages, to return a list of `Package`s this package manager is tracking
  # - implement #possible_package_paths, an array of `Pathname`s which are the possible locations which contain a configuration file/folder indicating the package manager is in use.
  # - implement(Optional) #package_management_command, string for invoking the package manager
  # - implement(Optional) #prepare_command, string for fetching dependencies for package manager (runs when the --prepare flag is passed to license_finder)

  class PackageManager
    include LicenseFinder::SharedHelpers

    class << self
      def takes_priority_over
        nil
      end

      def id
        name.split('::').last.downcase
      end
    end

    def installed?(logger = Core.default_logger)
      if package_management_command.nil?
        logger.debug self.class, 'no command defined' # TODO: comment me out
        true
      elsif command_exists?(package_management_command)
        logger.debug self.class, 'is installed', color: :green
        true
      else
        logger.info self.class, 'is not installed', color: :red
        false
      end
    end

    # see class description
    def package_management_command
      nil
    end

    # see class description
    def prepare_command
      nil
    end

    def command_exists?(command)
      _stdout, _stderr, status =
        if LicenseFinder::Platform.windows?
          Cmd.run("where #{command}")
        else
          Cmd.run("which #{command}")
        end

      status.success?
    end

    def initialize(options = {})
      @prepare_no_fail = options[:prepare_no_fail]
      @logger = options[:logger] || Core.default_logger
      @project_path = options[:project_path]
      @log_directory = options[:log_directory]
      @ignored_groups = options[:ignored_groups]
    end

    def active?
      path = detected_package_path
      path&.exist?
    end

    def project_root?
      active?
    end

    def detected_package_path
      possible_package_paths.find(&:exist?)
    end

    def prepare
      if prepare_command
        stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(prepare_command) }
        unless status.success?
          log_errors stderr

          error_message = "Prepare command '#{prepare_command}' failed\n#{stderr}"

          error_message == error_message.concat("\n#{stdout}\n") if !stdout.nil? && !stdout.empty?

          raise error_message unless @prepare_no_fail
        end
      else
        logger.debug self.class, 'no prepare step provided', color: :red
      end
    end

    def current_packages_with_relations
      begin
        packages = current_packages
      rescue StandardError => e
        raise e unless @prepare_no_fail

        packages = []
      end

      packages.each do |parent|
        parent.children.each do |child_name|
          child = packages.detect { |child_package| child_package.name == child_name }
          child.parents << parent.name if child
        end
      end
      packages
    end

    private

    attr_reader :logger, :project_path

    def log_errors(stderr)
      log_errors_with_cmd(prepare_command, stderr)
    end

    def log_errors_with_cmd(prep_cmd, stderr)
      logger.info(prep_cmd, 'did not succeed.', color: :red)
      logger.info(prep_cmd, stderr, color: :red)
      log_to_file(prep_cmd, stderr)
    end

    def log_to_file(prep_cmd, contents)
      FileUtils.mkdir_p @log_directory

      # replace whitespace with underscores and remove slashes
      log_file_name = package_management_command&.gsub(/\s/, '_')&.gsub(%r{/}, '')
      log_file = File.join(@log_directory, "prepare_#{log_file_name || 'errors'}.log")

      File.open(log_file, 'w') do |f|
        f.write("Prepare command \"#{prep_cmd}\" failed with:\n")
        f.write("#{contents}\n\n")
      end
    end
  end
end

require 'license_finder/package_managers/bower'
require 'license_finder/package_managers/go_workspace'
require 'license_finder/package_managers/go_15vendorexperiment'
require 'license_finder/package_managers/go_dep'
require 'license_finder/package_managers/gvt'
require 'license_finder/package_managers/glide'
require 'license_finder/package_managers/govendor'
require 'license_finder/package_managers/go_modules'
require 'license_finder/package_managers/trash'
require 'license_finder/package_managers/bundler'
require 'license_finder/package_managers/npm'
require 'license_finder/package_managers/pnpm'
require 'license_finder/package_managers/yarn'
require 'license_finder/package_managers/pip'
require 'license_finder/package_managers/pipenv'
require 'license_finder/package_managers/maven'
require 'license_finder/package_managers/mix'
require 'license_finder/package_managers/cocoa_pods'
require 'license_finder/package_managers/carthage'
require 'license_finder/package_managers/spm'
require 'license_finder/package_managers/gradle'
require 'license_finder/package_managers/rebar'
require 'license_finder/package_managers/erlangmk'
require 'license_finder/package_managers/nuget'
require 'license_finder/package_managers/dotnet'
require 'license_finder/package_managers/dep'
require 'license_finder/package_managers/conan'
require 'license_finder/package_managers/sbt'
require 'license_finder/package_managers/cargo'
require 'license_finder/package_managers/composer'
require 'license_finder/package_managers/conda'
require 'license_finder/package_managers/pub'

require 'license_finder/package'
